use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use super::{Index, NodeID, Term};
use crate::encoding;
use crate::error::Result;
use crate::storage;

/// A message envelope specifying the sender and receiver.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Envelope {
    /// The sender.
    pub from: NodeID,
    /// The sender's current term.
    pub term: Term,
    /// The recipient.
    pub to: NodeID,
    /// The message.
    pub message: Message,
}

impl encoding::Value for Envelope {}

/// A message sent between Raft nodes. Messages are sent asynchronously (i.e.
/// they are not request/response) and may be dropped or reordered.
///
/// In practice, they are sent across a TCP connection and crossbeam channel
/// which ensures messages are not dropped or reordered as long as the
/// connection remains intact. A message and its response are sent across
/// separate TCP connections (outbound from the respective sender).
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Message {

    /// A client request. This can be submitted to the leader, or to a follower
    /// which will forward it to its leader. If there is no leader, or the
    /// leader or term changes, the request is aborted with an Error::Abort
    /// ClientResponse and the client must retry.
    ClientRequest {
        /// The request ID. Must be globally unique for the request duration.
        id: RequestID,
        /// The request itself.
        request: Request,
    },

    /// A client response.
    ClientResponse {
        /// The ID of the original ClientRequest.
        id: RequestID,
        /// The response, or an error.
        response: Result<Response>,
    },
}

/// A client request ID. Must be globally unique while in flight.
///
/// For simplicity, a random UUIDv4 is used. We could incorporate the
/// node/process/MAC ID and timestamp for better collision avoidance (e.g. via
/// UUIDv6) but it doesn't matter at this scale.
pub type RequestID = uuid::Uuid;

/// A read sequence number, used to confirm leadership for linearizable reads.
pub type ReadSequence = u64;

/// A client request, typically passed through to the state machine.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Request {
    /// A state machine read command, executed via `State::read`. This is not
    /// replicated, and only evaluated on the leader.
    Read(Vec<u8>),
    /// A state machine write command, executed via `State::apply`. This is
    /// replicated across all nodes, and must produce a deterministic result.
    Write(Vec<u8>),
    /// Requests Raft cluster status from the leader.
    Status,
}

impl encoding::Value for Request {}

/// A client response. This will be wrapped in a Result for error handling.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Response {
    /// A state machine read result.
    Read(Vec<u8>),
    /// A state machine write result.
    Write(Vec<u8>),
    /// The current Raft leader status.
    Status(Status),
}

impl encoding::Value for Response {}

/// Raft cluster status. Generated by the leader.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Status {
    /// The current Raft leader, which generated this status.
    pub leader: NodeID,
    /// The current Raft term.
    pub term: Term,
    /// The match indexes of all nodes, indicating replication progress. Uses a
    /// BTreeMap for test determinism.
    pub match_index: BTreeMap<NodeID, Index>,
    /// The current commit index.
    pub commit_index: Index,
    /// The current applied index.
    pub applied_index: Index,
    /// The log storage engine status.
    pub storage: storage::Status,
}
